package _11_整理题目._8_多线程;

/**
 * https://www.bilibili.com/video/BV1N741127FH?p=4
 *
 * ThreadLocal 的作用，底层原理，应用场景，
 *
 * 作用：首先 ThreadLocal 是 线程本地变量 的意思，
 *      在多线程并发情况下，每个线程都会在线程内部保存一个变量的副本，
 *      后续的操作都是在自己的局部变量中操作，从而保证线程安全。
 *      ThreadLocal 有四个方法：initialValue get set remove
 *
 * 实现多个线程 set 变量，然后 get 变量，两次值一致性 怎么解决？
 * 使用 ThreadLocal 和 synchronized ，区别：
 *      首先相同点，它们都是用于处理多线程并发访问变量的问题，但是它们的处理思路是不同的
 *      synchronized 采用的时间换空间思路，只提供一份变量，让多个线程按一定顺序访问
 *      ThreadLocal 采用空间换时间的思路，提供多个副本，每个线程随意访问自己的副本变量
 *
 * 底层原理：
 * 底层如何实现它的作用
 *      每个线程 Thread 内部都有一个 Map 叫 ThreadLocalMap，这个 Map 和 hashMap 没有关系，
 *      是 ThreadLocal 单独实现的一个静态内部类，使用 Entry 数组 保存键值对数据，由 ThreadLocal 维护；
 *      一个 Entry 节点的 key 是一个 ThreadLocal 类型的弱引用，保存每个 ThreadLocal 变量，
 *      value 是实际保存的变量引用值。
 *
 *      当 set 一个值的时候，
 *      当哈希冲突时，使用 线性探测的 开放地址法，而不是拉链法
 *
 * ThreadLocal 的内存泄漏的原因，如何防止
 *      产生原因：
 *          内存泄漏是指内存分配出去后没有办法释放回收，导致的内存浪费
 *      避免方式：
 *          1、使用完后，调用 remove() 删除 变量，无论是强引用还是弱引用都不会内存泄漏
 *          2、使用完后，结束当前线程
 *      为什么使用弱引用：
 *          一共四种引用：强软弱虚，
 *              强引用指向的对象永远不会被回收，
 *              软引用指向的对象会在内存不够的时候被回收
 *              弱引用的对象只要发生垃圾回收事件就会被回收，
 *              虚引用指向的对象只是为了被回收的时候收到一条系统通知
 *          首先使用弱引用并不能完全避免内存泄漏，它是为了更好的垃圾回收
 *          首先它的引用链是 当先线程 -> ThreadLocal -> ThreadLocalMap -> Entry -> key,value
 *          当 Entry 的 key 是弱引用时候，下一次 gc 的时候就被回收了，置为 null，value 还是强引用
 *          那就出现了 内存泄漏，ThreadLocalMap 里有一个设计巧妙的地方是，当你下一次 调用 get set 的
 *          时候，它会去清楚 key 为 null 的 value，来避免内存泄漏
 *
 * 应用场景：
 *      当我们只想在本身的线程内使用的变量，可以用 ThreadLocal 来实现
 *      并且这些变量是和线程的生命周期密切相关的，线程结束，变量也就销毁了。
 */
public class _06_ThreadLocal_共享_ {

    private static ThreadLocal<Integer> age = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 666;
        }
    };

    public static void main(String[] args) throws Exception {

        // 主线程
        System.out.println(age.get());
        age.set(11);
        System.out.println(age.get());
        System.out.println();

        // 第一个线程
        Thread th = new Thread(() -> {
            System.out.println(age.get());
            age.set(22);
            System.out.println(age.get());
            System.out.println();
            age.remove();
        });
        th.start();
        th.join();

        // 第二个线程
        Thread th2 = new Thread(() -> {
            System.out.println(age.get());
            age.set(33);
            System.out.println(age.get());
            age.remove();
            System.out.println(age.get());
            System.out.println();
        });
        th2.start();
        th2.join();

        // 主线程
        System.out.println(age.get());
        age.remove();
    }

}
